// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 页堆。
// 
// 请参阅malloc.go以了解概述。

package runtime

import (
	"internal/cpu"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

const (
	// minPhysPageSize是物理页面大小的下限。
	// 实际物理页面大小可能大于此值。相反，
	// sys.PhysPageSize是物理页面大小的上限。
	minPhysPageSize = 4096

	// maxPhysPageSize是运行时支持的最大页面大小。
	maxPhysPageSize = 512 << 10

	// maxPhysHugePageSize设置运行时支持的最大巨大页面大小
	// 的上限。
	maxPhysHugePageSize = pallocChunkBytes

	// pagesperclaimerchunk表示一次从
	// pageInUse位图扫描多少页。由页回收器使用。
	// 
	// 更高的值可以减少扫描索引上的争用（例如
	// h.ReclainIndex），但会增加
	// 操作的最小延迟。
	// 
	// 扫描这么多页面所需的时间可能会有很大差异，具体取决于实际释放的跨距数。从实验上看，它可以在2.6GHz的核心i7上以~300GB/ms的速度扫描页面，但在~32MB/ms的速度下，它只能以~32MB/ms的速度扫描页面。使用512个页面将其限制在大约100µs。
	// 
	// 必须是PAGEIN使用位图元素大小的倍数，并且
	// 还必须将pagesPerArena等分。
	pagesPerReclaimerChunk = 512

	// physPageAlignedStacks指示堆栈分配是否必须与物理页对齐。这是
	// OpenBSD上的MAP_堆栈的要求。
	physPageAlignedStacks = GOOS == "openbsd"
)

// 主malloc堆。
// 堆本身是“free”和“scav”treaps，
// 但是所有其他全局数据也在这里。
// 
// mheap不能被堆分配，因为它包含mSpanLists，
// 不能被堆分配。
// 
// go:notinheap 
type mheap struct {
	// 必须仅在系统堆栈上获取锁，否则，如果g 
	// 的堆栈随着锁的保持而增长，则可能会发生自死锁。
	lock  mutex
	pages pageAlloc // 页面分配数据结构

	sweepgen     uint32 // 扫描生成，请参见mspan中的注释；在STW期间写入
	sweepDrained uint32 // 所有跨距都已扫描或正在扫描
	sweepers     uint32 // 活动扫描次数

	// 所有跨距都是所有创建的MSPAN的一部分。每个mspan 
	// 只出现一次。
	// 
	// allspans的内存是手动管理的，可以在堆增长时重新分配和移动。
	// 
	// 通常，所有跨度都受mheap_u2;.lock保护，该锁可防止并发访问并释放支持
	// 存储。STW期间的访问可能不会持有锁，但
	// 必须确保分配不会发生在
	// 访问周围（因为这可能会释放备份存储）。
	allspans []*mspan // 所有跨度

	_ uint32 // 将32位上的uint64字段用于原子

	// 比例扫描
	// 
	// 这些参数表示从gcController.heapLive 
	// 到页面扫描计数的线性函数。比例扫描系统通过将当前页面扫描计数
	// 保持在当前gcController.heapLive的此行上方，从而保持黑色。
	// 
	// 该线具有斜率扫掠PageSperByte，并通过一个
	// 基点（sweepHeapLiveBasis，pagesSweptBasis）。在任何给定的时间，系统在（gcController.heapLive，
	// /pagesSwept）这个空间中。
	// 
	// 线路通过我们控制的点
	// 而不是简单地从（0,0）原点开始
	// 这一点很重要，因为这样我们可以随时调整扫描速度，而
	// 考虑当前进度。如果我们只能调整
	// 斜率，如果任何
	// 已经取得进展，这将造成债务的不连续性。
	pagesInUse         uint64  // 统计mSpanInUse中的跨距页面；更新了原子
	pagesSwept         uint64  // 页面扫过本周期；更新原子
	pagesSweptBasis    uint64  // pagesSwept用作扫描比的原点；更新了gcController.heapLive的原子
	sweepHeapLiveBasis uint64  // 值，用作扫描比的原点；带锁写入，不带
	sweepPagesPerByte  float64 // 比例扫描比；带锁写入，不带
	// TODO（austin）：Page正弦应该是一个uintptr，但是386 
	// 编译器不能将字段对齐8字节。

	// Cleaventegoal是运行时将通过向操作系统返回内存
	// Heapretated度量）。
	// 来尝试维护的总保留堆内存量（由
	scavengeGoal uint64

	// 页面回收器状态

	// 回收索引是
	// 回收的下一页的所有页面中的页面索引。具体而言，它指的是arena allArenas[i/pagesPerArena]的页面（i%
	// pagesPerArena）。
	// 
	// 如果大于等于1<<63，则页面回收器将完成对页面标记的扫描。
	// 
	// 这是以原子方式访问的。
	reclaimIndex uint64
	// 回收积分是额外扫描页面的备用积分。由于
	// 页面回收器在大数据块中工作，因此它可能会回收超出请求的
	// 页面。任何发布的备用页面都将转到此
	// 信用池。
	// 
	// 这是以原子方式访问的。
	reclaimCredit uintptr

	// 竞技场是堆竞技场地图。它指向
	// 的元数据，即整个可用虚拟
	// 地址空间的每个竞技场帧的堆。
	// 
	// 使用arenaIndex将索引计算到此数组中。
	// 
	// 对于地址空间中不受
	// Go堆支持的区域，竞技场映射包含零。
	// 
	// 修改受mheap_u2;.lock保护。读取可以是
	// 执行，无需锁定；但是，当锁
	// 未被持有时，给定条目可以随时从nil转换为非nil。（条目从不转换回零。）
	// 
	// 通常，这是一个两级映射，由一个L1 
	// 映射和许多L2映射组成。当有大量的竞技场画面时，这样可以节省空间。然而，在许多
	// 平台（甚至64位）上，arenaL1Bits是0，这使得这个
	// 实际上是一个单级映射。在这种情况下，arenas[0]
	// 将永远不会为零。
	arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena

	// HEAPRENAALLOC是用于分配HEAPRENA 
	// 对象的预保留空间。这只在32位上使用，我们预先保留了
	// 这个空间，以避免它与堆本身交错。
	heapArenaAlloc linearAlloc

	// arenaHints是试图添加更多堆竞技场的地址列表。它最初由一组一般提示地址填充，并随着实际堆范围的增加而增加。
	arenaHints *arenaHint

	// 竞技场是一个预先保留的空间，用于分配堆竞技场
	// （实际竞技场）。这仅在32位上使用。
	arena linearAlloc

	// allArenas是每个地图竞技场的arenaIndex。这可以用来迭代地址空间。
	// 
	// 访问受mheap_u2;.lock保护。但是，由于这是
	// 仅追加，并且旧的备份数组永远不会被释放，因此获取mheap_.lock、复制切片头和
	// 然后释放mheap_.lock是安全的。
	allArenas []arenaIdx

	// sweepArenas是在
	// 扫描周期开始时拍摄的allArenas的快照。这可以通过
	// 简单地阻塞GC（通过禁用抢占）安全地读取。
	sweepArenas []arenaIdx

	// markArenas是在标记周期开始时拍摄的allArenas的快照。因为allArenas是append only，所以在标记过程中，该片及其内容都不会发生变化，因此可以安全读取。
	markArenas []arenaIdx

	// curArena是heap目前正在发展的竞技场
	// 。这应该始终与physPageSize对齐。
	curArena struct {
		base, end uintptr
	}

	_ uint32 // 确保中心

	// 小型类的中心可用列表的64位对齐。
	// 填充确保mcentral是
	// 将CacheLinePadSize字节隔开，以便每个mcentral.lock 
	// 都有自己的缓存线。
	// 中心由spanClass索引。
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}

	spanalloc             fixalloc // span*
	cachealloc            fixalloc // mcache的分配器*
	specialfinalizeralloc fixalloc // specialfinalizer的分配器*
	specialprofilealloc   fixalloc // specialprofile的分配器*
	specialReachableAlloc fixalloc // specialReachable 
	speciallock           mutex    // 特殊记录分配器的锁。
	arenaHintAlloc        fixalloc // arenaHints的分配器

	unused *specialfinalizer // 从未设置，只是在这里强制特殊finalizer类型为DWARF 
}

var mheap_ mheap

// heapArena存储堆竞技场的元数据。heapArenas存储在Go堆之外的
// 中，并通过mheap_.arenas索引进行访问。
// 
// go:notinheap 
type heapArena struct {
	// 位图存储
	// 此竞技场中单词的指针/标量位图。有关说明，请参见mbitmap.go。使用
	// heapBits类型访问此文件。
	bitmap [heapArenaBitmapBytes]byte

	// 将此竞技场中的虚拟地址页ID映射到*mspan。
	// 对于分配的跨距，其页面映射到跨距本身。
	// 对于自由跨距，只有最低和最高页面映射到跨距本身。
	// 内部页面映射到任意范围。
	// 对于从未分配的页面，spans条目为零。
	// 
	// 修改受mheap.lock保护。可以在不锁定的情况下执行
	// 读取，但只能从已知包含正在使用或堆栈范围的
	// 索引执行读取。这意味着在确定
	// 地址为活动地址和在spans数组中查找地址之间，不能存在安全点。
	spans [pagesPerArena]*mspan

	// pageInUse是一个位图，指示哪些跨距位于
	// 状态mSpanInUse中。此位图按页码
	// 编制索引，但仅使用每个
	// span中第一页对应的位。
	// 
	// 读写是原子的。
	pageInUse [pagesPerArena / 8]uint8

	// pageMarks是一个位图，指示哪些跨距上有任何标记的对象。与pageInUse一样，仅使用与每个跨距中的第一页对应的位
	// 。
	// 
	// 在标记期间以原子方式完成写入操作。读取是
	// 非原子且无锁的，因为它们仅在
	// 扫描期间发生（因此从不与写入竞争）。
	// 
	// 用于快速查找可以释放的整个跨度。
	// 
	// TODO（奥斯汀）：如果这是uint64用于
	// 更快的扫描，那就太好了，但我们没有64位原子位
	// 操作。
	pageMarks [pagesPerArena / 8]uint8

	// pageSpecials是一个位图，指示哪些跨区具有
	// specials（终结器或其他）。与pageInUse一样，仅使用与每个跨距中的第一页对应的位
	// 。
	// 
	// 每当将特殊项添加到某个跨距中，以及每当从跨距中删除最后一个特殊项时，都会以原子方式执行写入操作。
	// 在标记期间，以原子方式进行读取，以查找包含特殊项的跨距
	// 。
	pageSpecials [pagesPerArena / 8]uint8

	// 复选标记存储debug.gccheckmark状态。只有当debug.gccheckmark>0时，才使用
	// 。
	checkmarks *checkmarksMap

	// zeroedBase标记此
	// arena中第一页的第一个字节，该字节尚未使用，因此已为
	// zero。zeroedBase是相对于竞技场基座的。
	// 单调增加，直到达到平均值。
	// 
	// 此字段足以确定分配
	// 是否需要归零，因为页面分配器遵循
	// 地址顺序优先匹配策略。
	// 
	// 用原子CAS进行原子读写。
	zeroedBase uintptr
}

// arenaHint是关于堆竞技场在何处生长的提示。见
// mheap_uu2;.arenaHints。
// 
// go:notinheap 
type arenaHint struct {
	addr uintptr
	down bool
	next *arenaHint
}

// mspan是一个页面运行。
// 
// 当mspan处于无堆状态时，状态==MSPANFEE 
// 和heapmap（s->start）==span，heapmap（s->start+s->npages-1）==span。
// 如果mspan在堆scav treap中，那么除了上面的
// 之外，清除==true。清除==在所有其他情况下为false。
// 
// 分配mspan时，状态==MSPANINUUSE或mSpanManual 
// 和heapmap（i）==span for all s->start<=i<s->start+s->npages。

// 每个mspan都在一个双链接列表中，或者在mheap的
// 忙列表中，或者在mcentral的一个span列表中。

// 表示实际内存的mspan具有状态mSpanInUse、
// mSpanManual或MSPANFERE。这些状态之间的转换受到如下限制：在任何GC 
// /阶段，跨度可以从自由转换为使用中或手动。
// 
// *在扫描期间（gcphase==\u GCoff），量程可能从
// 用于释放（由于清扫）或手动释放（由于
// 堆栈被释放）。
// 
// *在GC（gcphase！=\u GCoff）期间，span*不得*从
// 手动或正在使用转换为自由。由于并发GC可以读取指针
// 然后查找其范围，因此范围状态必须是单调的。
// 
// 必须以原子方式将mspan.state设置为mSpanInUse或mSpanManual，并且只有在所有其他span字段有效后才能执行。
// 同样，如果检查跨度取决于它是否为
// mSpanInUse，则在依赖其他字段之前，应以原子方式加载状态并检查
// 状态。这允许垃圾收集器
// 安全地处理可能无效的指针，因为解析
// 这些指针可能会与分配的范围竞争。
type mSpanState uint8

const (
	mSpanDead   mSpanState = iota
	mSpanInUse             // 分配给垃圾收集堆
	mSpanManual            // 分配给手动管理（例如堆栈分配器）
)

// mSpanStateNames是范围状态的名称，由
// mSpanState索引。
var mSpanStateNames = []string{
	"mSpanDead",
	"mSpanInUse",
	"mSpanManual",
	"mSpanFree",
}

// mSpanStateBox包含一个mSpanState，并提供对它的原子操作。这是一个单独的类型，不允许与mSpanState进行意外比较或
// 赋值。
type mSpanStateBox struct {
	s mSpanState
}

func (b *mSpanStateBox) set(s mSpanState) {
	atomic.Store8((*uint8)(&b.s), uint8(s))
}

func (b *mSpanStateBox) get() mSpanState {
	return mSpanState(atomic.Load8((*uint8)(&b.s)))
}

// mSpanList是一个跨度链接列表的标题。
// 
// go:notinheap 
type mSpanList struct {
	first *mspan // 列表中的第一个跨距，如果没有，则为零，如果列表中的最后一个跨距没有，则为零，如果没有，则为零。
}

// go:notinheap 
type mspan struct {
	next *mspan     // 列表中的下一个跨距，或者如果没有
	prev *mspan     // 列表中的上一个跨距，则为零，如果没有
	list *mSpanList // 用于调试，则为零。TODO:删除。

	startAddr uintptr // span aka s.base（）的第一个字节地址
	npages    uintptr // span 

	manualFreeList gclinkptr // mSpanManual spans中的自由对象列表

	// 自由索引是0和nelems之间的插槽索引，在该索引处开始扫描
	// 以查找此跨度中的下一个自由对象。
	// 每个分配从freeindex开始扫描allocBits，直到遇到0 
	// 表示一个空闲对象。然后调整freeindex，以便后续扫描开始
	// 刚刚经过新发现的空闲对象。
	// 
	// 如果freeindex==nelem，则此范围没有可用对象。
	// 
	// allocBits是此范围内对象的位图。
	// 如果n>=freeindex和allocBits[n/8]&（1<<（n%8））为0 
	// 则对象n是自由的；
	// 否则，分配对象n。从nelem开始的位是
	// 未定义，不应引用。
	// 
	// 对象n从地址n*elemsize+开始（开始<pageShift）。
	freeindex uintptr
	// TODO:从sizeclass中查找nelems，如果此字段有助于提高性能，请删除它。
	nelems uintptr // 范围内的对象数。

	// 在freeindex上的allocBits缓存。allocCache移位为
	// 以便最低位对应于位空闲索引。
	// allocCache保留allocBits的补码，因此允许
	// ctz（计数尾随零）直接使用它。
	// allocache可能包含s.nelems以外的位；调用方必须忽略
	// 这些。
	allocCache uint64

	// allocBits和gcmarkBits保存指向跨距标记的指针，
	// 分配位。指针是8字节对齐的。
	// 保存此数据的领域有三个。
	// 免费：不再访问的脏竞技场
	// 可以重复使用。
	// next:保存下一个GC循环中使用的信息。
	// 当前：此GC循环期间使用的信息。
	// 上一个：在上一个GC循环中使用的信息。
	// 一个新的GC循环从finishsweep\m调用开始。
	// finishsweep\m将前一个竞技场移到免费竞技场，
	// 将当前竞技场移到前一个竞技场，将下一个竞技场移到当前竞技场。
	// 下一个竞技场在spans请求
	// 内存为下一个GC循环保存gcmarkBits以及
	// 作为新分配的spans的allocBits时填充。
	// 
	// 指针运算是“手动”完成的，而不是使用
	// 数组来避免沿关键性能
	// 路径进行边界检查。
	// 扫描将释放旧的allocBits并将allocBits设置为
	// gcmarkBits。gcmarkBits替换为新的归零
	// 输出内存。
	allocBits  *gcBits
	gcmarkBits *gcBits

	// 扫描生成：
	// 如果sweepgen==h->sweepgen-2，则跨度需要扫描
	// 如果sweepgen==h->sweepgen-1，则跨度当前正在扫描
	// 如果sweepgen==h->sweepgen，则跨度已扫描并准备好使用
	// 如果sweepgen==h->sweepgen+1，span在扫描开始之前已缓存，并且仍在缓存中，需要扫描
	// 如果sweepgen==h->sweepgen+3，span被扫描然后被缓存并且仍然被缓存
	// h->sweepgen在每次GC 

	sweepgen    uint32
	divMul      uint32        // for除以ELEMISIZE 
	allocCount  uint16        // 分配对象数
	spanclass   spanClass     // size class和noscan（uint8）
	state       mSpanStateBox // mSpanInUse等后增加2；原子访问（get/set方法）
	needzero    uint8         // 分配前需要归零
	elemsize    uintptr       // 从sizeclass或npages 
	limit       uintptr       // span 
	speciallock mutex         // guards specials list 
	specials    *special      // 按偏移量排序的特殊记录链接列表。
}

func (s *mspan) base() uintptr {
	return s.startAddr
}

func (s *mspan) layout() (size, n, total uintptr) {
	total = s.npages << _PageShift
	size = s.elemsize
	if size > 0 {
		n = total / size
	}
	return
}

// recordspan将新分配的span添加到h.allspan。
// 
// 这仅在第一次从
// mheap.spanalloc分配范围时发生（重复使用范围时不调用）。
// 
// 此处不允许使用写屏障，因为在分配新工作buf时可以从
// gcWork调用写屏障。但是，因为它是来自fixalloc初始值设定项的间接调用，所以编译器无法看到这个。
// 
// 必须持有堆锁。
// 
// go:nowritebarrierrec 
func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
	h := (*mheap)(vh)
	s := (*mspan)(p)

	assertLockHeld(&h.lock)

	if len(h.allspans) >= cap(h.allspans) {
		n := 64 * 1024 / sys.PtrSize
		if n < cap(h.allspans)*3/2 {
			n = cap(h.allspans) * 3 / 2
		}
		var new []*mspan
		sp := (*slice)(unsafe.Pointer(&new))
		sp.array = sysAlloc(uintptr(n)*sys.PtrSize, &memstats.other_sys)
		if sp.array == nil {
			throw("runtime: cannot allocate memory")
		}
		sp.len = len(h.allspans)
		sp.cap = n
		if len(h.allspans) > 0 {
			copy(new, h.allspans)
		}
		oldAllspans := h.allspans
		*(*notInHeapSlice)(unsafe.Pointer(&h.allspans)) = *(*notInHeapSlice)(unsafe.Pointer(&new))
		if len(oldAllspans) != 0 {
			sysFree(unsafe.Pointer(&oldAllspans[0]), uintptr(cap(oldAllspans))*unsafe.Sizeof(oldAllspans[0]), &memstats.other_sys)
		}
	}
	h.allspans = h.allspans[:len(h.allspans)+1]
	h.allspans[len(h.allspans)-1] = s
}

// 一个跨度类别代表一个跨度的大小类别和大小。
// 
// 每个尺寸类别都有一个noscan Span类别和一个scan Span类别。
// noscan span类只包含noscan对象，这些对象不包含
// 指针，因此不需要垃圾
// 收集器进行扫描。
type spanClass uint8

const (
	numSpanClasses = _NumSizeClasses << 1
	tinySpanClass  = spanClass(tinySizeClass<<1 | 1)
)

func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
	return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}

func (sc spanClass) sizeclass() int8 {
	return int8(sc >> 1)
}

func (sc spanClass) noscan() bool {
	return sc&1 != 0
}

// arenaIndex将索引返回到竞技场的mheap_.arenas中
// 包含p的元数据。此索引将一个索引合并到
// L1映射和一个索引合并到L2映射，并应用作
// mheap_.arenas[ai.L1（）][ai.L2（）]。
// 
// 如果p超出有效堆地址的范围，则l1（）或
// l2（）将超出范围。
// 
// 它是nosplit，因为它是由spanOf和其他几个
// nosplit函数调用的。
// 
// go:nosplit 
func arenaIndex(p uintptr) arenaIdx {
	return arenaIdx((p - arenaBaseOffset) / heapArenaBytes)
}

// arenaBase返回堆
// arena i所覆盖区域的低位地址。
func arenaBase(i arenaIdx) uintptr {
	return uintptr(i)*heapArenaBytes + arenaBaseOffset
}

type arenaIdx uint

func (i arenaIdx) l1() uint {
	if arenaL1Bits == 0 {
		// 如果没有
		// L1映射，就让编译器对其进行优化。
		return 0
	} else {
		return uint(i) >> arenaL1Shift
	}
}

func (i arenaIdx) l2() uint {
	if arenaL1Bits == 0 {
		return uint(i)
	} else {
		return uint(i) & (1<<arenaL2Bits - 1)
	}
}

// inheap报告b是否是指向（可能已死亡）堆对象的指针。
// 对于指向mSpanManual跨度的指针，返回false。
// 不可抢占，因为它被写屏障使用。
// go:nowritebarrier 
// go:nosplit 
func inheap(b uintptr) bool {
	return spanOfHeap(b) != nil
}

// inHeapOrStack是inheap的一个变体，对于任何分配的堆跨度中的指针返回true。
// 
// go:nowritebarrier 
// go:nosplit 
func inHeapOrStack(b uintptr) bool {
	s := spanOf(b)
	if s == nil || b < s.base() {
		return false
	}
	switch s.state.get() {
	case mSpanInUse, mSpanManual:
		return b < s.limit
	default:
		return false
	}
}

// spanOf返回p的跨度。如果p没有指向堆
// arena，或者没有span包含p，则spanOf返回nil。
// 
// 如果p未指向已分配内存，则可能返回一个非零的
// 跨度，该跨度*不*包含p。如果这是一种可能性，则调用方应该显式地调用span OFHEAP或检查span界限
// 。
// 
// 必须是nosplit，因为它的调用方是nosplit。
// 
// go:nosplit 
func spanOf(p uintptr) *mspan {
	// 此函数看起来很大，但我们使用了大量常量
	// 围绕arenaL1Bits进行折叠，以将其放入内联
	// 预算中。另外，这里的许多检查都是安全检查
	// Go无论如何都需要执行，因此生成的代码非常简短。
	ri := arenaIndex(p)
	if arenaL1Bits == 0 {
		// 如果没有L1，则ri.L1（）不能超出范围，但ri.l2（）可以。
		if ri.l2() >= uint(len(mheap_.arenas[0])) {
			return nil
		}
	} else {
		// 如果存在L1，则ri.L1（）可以超出范围，但ri.l2（）不能。
		if ri.l1() >= uint(len(mheap_.arenas)) {
			return nil
		}
	}
	l2 := mheap_.arenas[ri.l1()]
	if arenaL1Bits != 0 && l2 == nil { // 如果没有L1，就永远不会发生。
		return nil
	}
	ha := l2[ri.l2()]
	if ha == nil {
		return nil
	}
	return ha.spans[(p/pageSize)%pagesPerArena]
}

// spanOfUnchecked等同于spanOf，但调用方必须确保
// p指向已分配的堆。
// 
// 必须是nosplit，因为它的调用者是nosplit。
// 
// go:nosplit 
func spanOfUnchecked(p uintptr) *mspan {
	ai := arenaIndex(p)
	return mheap_.arenas[ai.l1()][ai.l2()].spans[(p/pageSize)%pagesPerArena]
}

// spanOfHeap类似于spanOf，但如果p不指向
// 堆对象，则返回nil。
// 
// 必须是nosplit，因为它的调用者是nosplit。
// 
// go:nosplit 
func spanOfHeap(p uintptr) *mspan {
	s := spanOf(p)
	// s如果从未分配过，则为零。否则，我们首先检查
	// 它的状态，因为我们不信任这个指针，所以我们
	// 必须与span初始化同步。然后，它是
	// 仍然可能我们拾取了一个陈旧的span指针，因此我们必须检查span的边界。
	if s == nil || s.state.get() != mSpanInUse || p < s.base() || p >= s.limit {
		return nil
	}
	return s
}

// pageIndexOf返回指针p的区域、页面索引和页面掩码。
// 调用方必须确保p在堆中。
func pageIndexOf(p uintptr) (arena *heapArena, pageIdx uintptr, pageMask uint8) {
	ai := arenaIndex(p)
	arena = mheap_.arenas[ai.l1()][ai.l2()]
	pageIdx = ((p / pageSize) / 8) % uintptr(len(arena.pageInUse))
	pageMask = byte(1 << ((p / pageSize) % 8))
	return
}

// 初始化堆。
func (h *mheap) init() {
	lockInit(&h.lock, lockRankMheap)
	lockInit(&h.speciallock, lockRankMheapSpecial)

	h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
	h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys)
	h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
	h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
	h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys)
	h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys)

	// 不要将mspan分配归零。背景扫描可以
	// 在分配span的同时检查span，因此在释放
	// 并重新分配span以防止背景扫描
	// 不正确地从0中查找span时，span的扫描根仍然存在是很重要的。
	// 
	// 这是安全的，因为mspan不包含堆指针。
	h.spanalloc.zero = false

	// h->mapcache不需要初始化

	for i := range h.central {
		h.central[i].mcentral.init(spanClass(i))
	}

	h.pages.init(&h.lock, &memstats.gcMiscSys)
}

// 回收扫描并将至少一页页回收到堆中。
// 在分配npage页面之前调用它，以控制增长。
// 
// Recall实现了页面回收器的一半。不能持有锁。
func (h *mheap) reclaim(npage uintptr) {
	// TODO（austin）：释放跨距所花费的时间有一半是在
	// 锁定/解锁堆（即使争用较低）。我们
	// 通过
	// 批处理堆释放，可以使这里的慢路径快几倍。

	// 如果没有更多的回收工作，请提前保释。
	if atomic.Load64(&h.reclaimIndex) >= 1<<63 {
		return
	}

	// 禁用抢占，这样GC在我们执行
	// 扫描时无法启动，这样我们就可以读取h.sweepArenas，所以
	// 在P.
	mp := acquirem()

	if trace.enabled {
		traceGCSweepStart()
	}

	arenas := h.sweepArenas
	locked := false
	for npage > 0 {
		// 上的traceGCSweepStart/Done对首先从累积信用中提取。只带我们需要的东西。
		if credit := atomic.Loaduintptr(&h.reclaimCredit); credit > 0 {
			take := credit
			if take > npage {
				take = npage
			}
			if atomic.Casuintptr(&h.reclaimCredit, credit, credit-take) {
				npage -= take
			}
			continue
		}

		// 申请一大块工作。
		idx := uintptr(atomic.Xadd64(&h.reclaimIndex, pagesPerReclaimerChunk) - pagesPerReclaimerChunk)
		if idx/pagesPerArena >= uintptr(len(arenas)) {
			// 页面回收完成。
			atomic.Store64(&h.reclaimIndex, 1<<63)
			break
		}

		if !locked {
			// 锁定堆以回收块。
			lock(&h.lock)
			locked = true
		}

		// 扫描此块。
		nfound := h.reclaimChunk(arenas, idx, pagesPerReclaimerChunk)
		if nfound <= npage {
			npage -= nfound
		} else {
			// 将备用页放入全球信贷。
			atomic.Xadduintptr(&h.reclaimCredit, nfound-npage)
			npage = 0
		}
	}
	if locked {
		unlock(&h.lock)
	}

	if trace.enabled {
		traceGCSweepDone()
	}
	releasem(mp)
}

// 回收从页面索引开始的未标记跨距[pageIdx，pageIdx+n）.
// 返回到堆中的页数。
// 
// h.lock必须保持，且调用方必须不可抢占。注意：h.lock可能是
// 临时解锁并重新锁定，以便进行扫描，或者如果启用了跟踪
// 
func (h *mheap) reclaimChunk(arenas []arenaIdx, pageIdx, n uintptr) uintptr {
	// 必须保持堆锁，因为这将使用潜在的非活动指针访问
	// heapArena.span数组。
	// 特别是，如果一个span被释放并与此探测heapArena.span并发合并
	// 则可以使用
	// 观察任意值y、 陈旧的跨距指针。
	assertLockHeld(&h.lock)

	n0 := n
	var nFreed uintptr
	sl := newSweepLocker()
	for n > 0 {
		ai := arenas[pageIdx/pagesPerArena]
		ha := h.arenas[ai.l1()][ai.l2()]

		// 获取一块要处理的位图。
		arenaPage := uint(pageIdx % pagesPerArena)
		inUse := ha.pageInUse[arenaPage/8:]
		marked := ha.pageMarks[arenaPage/8:]
		if uintptr(len(inUse)) > n/8 {
			inUse = inUse[:n/8]
			marked = marked[:n/8]
		}

		// 扫描此位图块以查找正在使用的跨距
		// 但其上没有标记的对象。
		for i := range inUse {
			inUseUnmarked := atomic.Load8(&inUse[i]) &^ marked[i]
			if inUseUnmarked == 0 {
				continue
			}

			for j := uint(0); j < 8; j++ {
				if inUseUnmarked&(1<<j) != 0 {
					s := ha.spans[arenaPage+uint(i)*8+j]
					if s, ok := sl.tryAcquire(s); ok {
						npages := s.npages
						unlock(&h.lock)
						if s.sweep(false) {
							nFreed += npages
						}
						lock(&h.lock)
						// 重新加载使用中。可能是附近的
						// 当我们丢弃
						// 锁定，我们不想从spans数组中获取过时的
						// 指针。
						inUseUnmarked = atomic.Load8(&inUse[i]) &^ marked[i]
					}
				}
			}
		}

		// 高级。
		pageIdx += uintptr(len(inUse) * 8)
		n -= uintptr(len(inUse) * 8)
	}
	sl.dispose()
	if trace.enabled {
		unlock(&h.lock)
		// 已扫描但未回收的页面的帐户。返回时必须锁定
		traceGCSweepSpan((n0 - nFreed) * pageSize)
		lock(&h.lock)
	}

	assertLockHeld(&h.lock) // 
	return nFreed
}

// spanAllocType表示要进行的分配类型，或
// 要进行的分配类型要释放的分配。
type spanAllocType uint8

const (
	spanAllocHeap          spanAllocType = iota // 堆span 
	spanAllocStack                              // 堆栈span 
	spanAllocPtrScalarBits                      // 展开的GC程序位图span 
	spanAllocWorkBuf                            // 工作buf span 
)

// 如果跨度分配是手动管理的，则手动返回true。
func (s spanAllocType) manual() bool {
	return s != spanAllocHeap
}

// alloc从GC的d堆分配新的npage页跨度。
// 
// spanclass表示span的大小类别和可扫描性。
// 
// 如果needzero为true，则返回span的内存将为零。
// 返回的布尔值表示返回的span是否包含零，
// 因为这是请求d、 或者因为它已经归零。
func (h *mheap) alloc(npages uintptr, spanclass spanClass, needzero bool) (*mspan, bool) {
	// 不要执行任何锁定G堆栈上堆的操作。
	// 可能会触发堆栈增长，堆栈增长代码需要
	// 才能分配堆。
	var s *mspan
	systemstack(func() {
		// 为了防止堆过度增长，在分配n个页面之前
		// 我们需要扫描和回收至少n个页面。
		if !isSweepDone() {
			h.reclaim(npages)
		}
		s = h.allocSpan(npages, spanAllocHeap, spanclass)
	})

	if s == nil {
		return nil, false
	}
	isZeroed := s.needzero == 0
	if needzero && !isZeroed {
		memclrNoHeapPointers(unsafe.Pointer(s.base()), s.npages<<_PageShift)
		isZeroed = true
	}
	s.needzero = 0
	return s, isZeroed
}

// allocManual分配手动管理的npage页面范围。
// 如果分配失败，allocManual返回零。
// 
// allocManual添加用于*stat的字节，该字节应为
// memstats in use字段。与GC'd堆中的分配不同，
// 分配*不*计入heap\u inuse或heap\u sys。
// 
// 如果设置了
// span.needzero，则支持返回范围的内存可能不会归零。
// 
// 必须在系统堆栈上调用allocManual，因为它可能通过AllocPan获取堆锁。有关详细信息，请参见mheap。
// 
// 如果编写了新代码来调用allocManual，请不要使用
// 现有的spanAllocType值，而是声明一个新值。
// 
// go:systemstack 
func (h *mheap) allocManual(npages uintptr, typ spanAllocType) *mspan {
	if !typ.manual() {
		throw("manual span allocation called with non-manually-managed type")
	}
	return h.allocSpan(npages, typ, 0)
}

// setSpans修改跨度映射，使[spanOf（base），spanOf（base+npage*pageSize）]
// 为s。
func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
	p := base / pageSize
	ai := arenaIndex(base)
	ha := h.arenas[ai.l1()][ai.l2()]
	for n := uintptr(0); n < npage; n++ {
		i := (p + n) % pagesPerArena
		if i == 0 {
			ai = arenaIndex(base + n*pageSize)
			ha = h.arenas[ai.l1()][ai.l2()]
		}
		ha.spans[i] = s
	}
}

// allocNeedsZero检查地址空间区域[base，base+npage*pageSize），
// 假定要分配，需要归零，为
// 将来的分配更新堆竞技场元数据。
// 
// 每次从堆中分配页时都必须调用此函数，即使页面
// 分配器可以证明内存不足它的分配已经为零，因为它们刚从操作系统中删除。它更新了对未来页面分配至关重要的heapArena元数据。
// 
// 此方法没有锁定约束。
func (h *mheap) allocNeedsZero(base, npage uintptr) (needZero bool) {
	for npage > 0 {
		ai := arenaIndex(base)
		ha := h.arenas[ai.l1()][ai.l2()]

		zeroedBase := atomic.Loaduintptr(&ha.zeroedBase)
		arenaBase := base % heapArenaBytes
		if arenaBase < zeroedBase {
			// 我们扩展到了
			// 竞技场，所以这个区域在使用前需要归零。
			// 
			// zeroedBase是单调递增的，所以如果我们现在看到这个，那么
			// 我们可以确定我们需要将这个内存区域归零。
			// 
			// 我们仍然需要更新zeroedBase以供使用s arena和
			// 可能还有更多的arena。
			needZero = true
		}
		// 如果我们与地址
		// 分配竞赛，我们可能会观察到arenaBase>zeroedBase。但是，因为我们知道没有其他人获取*这个*内存，所以它是
		// 空间中直接在我们前面获取内存的一个或多个
		// 仍然安全到不为零。

		// 计算我们延伸到竞技场的距离，封顶
		// 在heapArenaBytes.
		arenaLimit := arenaBase + npage*pageSize
		if arenaLimit > heapArenaBytes {
			arenaLimit = heapArenaBytes
		}
		// 增加ha.zeroledbase，使其>=arenaLimit.
		// 我们可能正在与其他更新赛跑。
		for arenaLimit > zeroedBase {
			if atomic.Casuintptr(&ha.zeroedBase, zeroedBase, arenaLimit) {
				break
			}
			zeroedBase = atomic.Loaduintptr(&ha.zeroedBase)
			// 检查zeroledbase.
			if zeroedBase <= arenaLimit && zeroedBase > arenaBase {
				// zeroedBase移动到了我们试图声称的空间中。这非常糟糕，并且表明有人分配了与我们相同的区域。将base向前移动并从npage中减去，移动到下一个竞技场，或完成。
				throw("potentially overlapping in-use allocations detected")
			}
		}

		base += arenaLimit - arenaBase
		npage -= (arenaLimit - arenaBase) / pageSize
	}
	return
}

// tryAllocMSpan尝试从P-local缓存中分配mspan对象，但可能会失败。
// 
// h.lock不需要保持。
// 
// 此调用程序必须确保其P在此函数期间不会在
// it下更改。目前，我们要确保
// 该函数是在系统堆栈上运行的，因为这是
// 现在唯一使用它的地方。将来，如果需要在其他地方使用，这个要求
// 可能会放宽。
// 
// go:systemstack 
func (h *mheap) tryAllocMSpan() *mspan {
	pp := getg().m.p.ptr()
	// 如果我们没有p或cache为空，我们不能在这里执行任何操作。
	if pp == nil || pp.mspancache.len == 0 {
		return nil
	}
	// 删除缓存中的最后一个条目。
	s := pp.mspancache.buf[pp.mspancache.len-1]
	pp.mspancache.len--
	return s
}

// AllocmSpanlock分配一个mspan对象。
// 
// h。必须保持锁定。
// 
// 必须在系统堆栈上调用AllocmSpanlock beca使用
// 其调用者持有堆锁。有关详细信息，请参阅mheap。在系统堆栈上运行
// 还可以确保我们不会在该函数期间切换Ps。有关详细信息，请参阅tryAllocMSpan。
// 
// go:systemstack 
func (h *mheap) allocMSpanLocked() *mspan {
	assertLockHeld(&h.lock)

	pp := getg().m.p.ptr()
	if pp == nil {
		// 我们没有p，所以不要这样做他是正常的。
		return (*mspan)(h.spanalloc.alloc())
	}
	// 必要时重新填充缓存。
	if pp.mspancache.len == 0 {
		const refillCount = len(pp.mspancache.buf) / 2
		for i := 0; i < refillCount; i++ {
			pp.mspancache.buf[i] = (*mspan)(h.spanalloc.alloc())
		}
		pp.mspancache.len = refillCount
	}
	// 提取缓存中的最后一个条目。
	s := pp.mspancache.buf[pp.mspancache.len-1]
	pp.mspancache.len--
	return s
}

// freeMSpanLocked释放mspan对象。
// 
// h.必须持有锁。
// 
// 必须在系统堆栈上调用freeMSpanLocked，因为
// 其调用者持有堆锁。有关详细信息，请参见mheap。
// 在系统堆栈上运行也可以确保我们不会在该函数期间切换Ps。有关详细信息，请参阅tryAllocMSpan。
// 
// go:systemstack 
func (h *mheap) freeMSpanLocked(s *mspan) {
	assertLockHeld(&h.lock)

	pp := getg().m.p.ptr()
	// 首先尝试将mspan直接释放到缓存中。
	if pp != nil && pp.mspancache.len < len(pp.mspancache.buf) {
		pp.mspancache.buf[pp.mspancache.len] = s
		pp.mspancache.len++
		return
	}
	// 如果失败（或者如果我们没有p），只需将其释放到堆中即可。
	h.spanalloc.free(unsafe.Pointer(s))
}

// allocSpan分配一个拥有相当于npages内存的mspan。
// 
// 如果typ.manual（）==false，AllocPan将分配类spanclass 
// 的堆范围，并更新堆记帐。如果manual==true，AllocPan将分配一个
// 手动管理的span（忽略span类），调用方是
// 负责与其span使用相关的任何记帐。无论是
// 方式，allocSpan都会自动将新分配的
// span中的字节添加到*sysStat。
// 
// 返回的范围已完全初始化。
// 
// h.锁不能被持有。
// 
// 必须在系统堆栈上调用allocSpan，因为它获取了
// 堆锁，并且必须阻止GC转换。
// 
// go:systemstack 
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
	// 函数全局状态。
	gp := getg()
	base, scav := uintptr(0), uintptr(0)

	// 在某些平台上，我们需要提供物理页面对齐堆栈
	// 分配。如果页面大小小于物理页面
	// size，则默认情况下我们已经能够做到这一点。
	needPhysPageAlign := physPageAlignedStacks && typ == spanAllocStack && pageSize < physPageSize

	// 如果分配足够小，请尝试页面缓存！
	// 页面缓存不支持对齐分配，因此如果需要提供物理页面对齐堆栈分配，则无法使用
	// 它。
	pp := gp.m.p.ptr()
	if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
		c := &pp.pcache

		// 如果缓存为空，请重新填充。
		if c.empty() {
			lock(&h.lock)
			*c = h.pages.allocToCache()
			unlock(&h.lock)
		}

		// 尝试从缓存进行分配。
		base, scav = c.alloc(npages)
		if base != 0 {
			s = h.tryAllocMSpan()
			if s != nil {
				goto HaveSpan
			}
			// 我们有一个基，但没有mspan，所以我们需要
			// 来锁定堆。
		}
	}

	// 出于这样或那样的原因，如果没有堆锁，我们无法完成整个作业。
	lock(&h.lock)

	if needPhysPageAlign {
		// 通过物理页面过度分配，以允许以后的对齐。
		npages += physPageSize / pageSize
	}

	if base == 0 {
		// 尝试获取基址。
		base, scav = h.pages.alloc(npages)
		if base == 0 {
			if !h.grow(npages) {
				unlock(&h.lock)
				return nil
			}
			base, scav = h.pages.alloc(npages)
			if base == 0 {
				throw("grew heap, but no adequate free space found")
			}
		}
	}
	if s == nil {
		// 我们之前无法获取mspan，因此，现在我们有了堆锁，请抓取一个。
		s = h.allocMSpanLocked()
	}

	if needPhysPageAlign {
		allocBase, allocPages := base, npages
		base = alignUp(allocBase, physPageSize)
		npages -= physPageSize / pageSize

		// 返回对齐分配周围的内存。
		spaceBefore := base - allocBase
		if spaceBefore > 0 {
			h.pages.free(allocBase, spaceBefore/pageSize)
		}
		spaceAfter := (allocPages-npages)*pageSize - spaceBefore
		if spaceAfter > 0 {
			h.pages.free(base+npages*pageSize, spaceAfter/pageSize)
		}
	}

	unlock(&h.lock)

HaveSpan:
	// 此时，两个s！=零和基数！=0，并且堆
	// 锁不再保持。初始化跨度。
	s.init(base, npages)
	if h.allocNeedsZero(base, npages) {
		s.needzero = 1
	}
	nbytes := npages * pageSize
	if typ.manual() {
		s.manualFreeList = 0
		s.nelems = 0
		s.limit = s.base() + s.npages*pageSize
		s.state.set(mSpanManual)
	} else {
		// 我们必须先设置span属性，然后才能将span发布到任何位置
		// 因为我们没有持有堆锁。
		s.spanclass = spanclass
		if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
			s.elemsize = nbytes
			s.nelems = 1
			s.divMul = 0
		} else {
			s.elemsize = uintptr(class_to_size[sizeclass])
			s.nelems = nbytes / s.elemsize
			s.divMul = class_to_divmagic[sizeclass]
		}

		// 初始化标记和分配结构。
		s.freeindex = 0
		s.allocCache = ^uint64(0) // 所有1表示所有空闲。
		s.gcmarkBits = newMarkBits(s.nelems)
		s.allocBits = newAllocBits(s.nelems)

		// 在不使用堆锁的情况下访问h.sweepgen是安全的，因为它是
		// 仅在世界停止时更新，我们在
		// 系统堆栈上运行，该堆栈阻止STW转换。
		atomic.Store(&s.sweepgen, h.sweepgen)

		// 既然已填入范围，请设置其状态。此
		// 是范围中其他字段的发布屏障。虽然到该span的有效指针在返回span之前不应可见，但如果垃圾收集器发现无效指针，则对span的访问可能与对该span的初始化竞争。我们通过原子化
		// 在span完全初始化
		// 之后设置状态，并在
		// 中原子化检查任何怀疑指针的情况下的状态来解决此争用。
		s.state.set(mSpanInUse)
	}

	// 提交并说明span现在拥有的任何已清除内存。
	if scav != 0 {
		// 系统使用了范围内所有实际可用的页面，因为其中一些页面可能会被清除。
		sysUsed(unsafe.Pointer(base), nbytes)
		atomic.Xadd64(&memstats.heap_released, -int64(scav))
	}
	// 更新统计信息。
	if typ == spanAllocHeap {
		atomic.Xadd64(&memstats.heap_inuse, int64(nbytes))
	}
	if typ.manual() {
		// 手动管理的内存不计入堆系统。
		memstats.heap_sys.add(-int64(nbytes))
	}
	// 更新一致的统计信息。
	stats := memstats.heapStats.acquire()
	atomic.Xaddint64(&stats.committed, int64(scav))
	atomic.Xaddint64(&stats.released, -int64(scav))
	switch typ {
	case spanAllocHeap:
		atomic.Xaddint64(&stats.inHeap, int64(nbytes))
	case spanAllocStack:
		atomic.Xaddint64(&stats.inStacks, int64(nbytes))
	case spanAllocPtrScalarBits:
		atomic.Xaddint64(&stats.inPtrScalarBits, int64(nbytes))
	case spanAllocWorkBuf:
		atomic.Xaddint64(&stats.inWorkBufs, int64(nbytes))
	}
	memstats.heapStats.release()

	// 在不同位置发布跨度。

	// 在不持有锁的情况下调用是安全的，因为与此范围相关的插槽
	// 只能由
	// 此线程读取或修改，直到该范围内的指针被发布为止（以及
	// 我们在函数
	// 结束时执行一个发布屏障，否则将更新pageInUse。
	h.setSpans(s.base(), npages, s)

	if !typ.manual() {
		// 在竞技场页面位图中标记正在使用的跨距。
		// 
		// 这会将跨距发布到页面清理程序，因此
		// 必须在此行之前完全初始化跨距。
		arena, pageIdx, pageMask := pageIndexOf(s.base())
		atomic.Or8(&arena.pageInUse[pageIdx], pageMask)

		// 更新相关页面清理程序统计信息。
		atomic.Xadd64(&h.pagesInUse, int64(npages))
	}

	// 在发布指向范围的指针之前，确保GC将观察到新分配的范围。
	publicationBarrier()

	return s
}

// 尝试向堆中添加至少N个页面的内存，返回是否有效。
// 
// h.锁必须保持。
func (h *mheap) grow(npage uintptr) bool {
	assertLockHeld(&h.lock)

	// 我们必须将堆分成整个palloc块。
	// 我们在下面调用sysMap，但请注意，因为我们将pallocChunkPages集合到MiB的顺序
	// 上（通常>=到巨大的页面大小），所以
	// 不会调用太多。
	ask := alignUp(npage, pallocChunkPages) * pageSize

	totalGrowth := uintptr(0)
	// 这可能会溢出，因为ask可能非常大
	// 并且与h.curArena.base无关。
	end := h.curArena.base + ask
	nBase := alignUp(end, physPageSize)
	if nBase > h.curArena.end || /* overflow */ end < h.curArena.base {
		// 当前竞技场空间不足。分配更多
		// 竞技场空间。这可能与
		// 当前竞技场不相邻，因此我们必须请求完整的ask。
		av, asize := h.sysAlloc(ask)
		if av == nil {
			print("runtime: out of memory: cannot allocate ", ask, "-byte block (", memstats.heap_sys, " in use)\n")
			return false
		}

		if uintptr(av) == h.curArena.end {
			// 新空间与旧
			// 空间相邻，因此只需扩展当前空间即可。
			h.curArena.end = uintptr(av) + asize
		} else {
			// 新空间不连续。跟踪当前空间的剩余内容，并切换到新空间。这应该是罕见的。
			if size := h.curArena.end - h.curArena.base; size != 0 {
				// 将此空间从保留转换为已准备，并将其标记为已发布，因为我们可以在更新页面分配器并随时释放锁后开始使用它。
				sysMap(unsafe.Pointer(h.curArena.base), size, &memstats.heap_sys)
				// 更新统计信息。
				atomic.Xadd64(&memstats.heap_released, int64(size))
				stats := memstats.heapStats.acquire()
				atomic.Xaddint64(&stats.released, int64(size))
				memstats.heapStats.release()
				// 更新页面分配器的结构，使此
				// 空间准备好分配。
				h.pages.grow(h.curArena.base, size)
				totalGrowth += size
			}
			// 切换到新空间。
			h.curArena.base = uintptr(av)
			h.curArena.end = uintptr(av) + asize
		}

		// 重新计算nBase。
		// 我们知道这不会溢出，因为sysAlloc返回了
		// 一个从h.curArena.base开始的有效区域，其大小为
		// 最小ask字节。
		nBase = alignUp(h.curArena.base+ask, physPageSize)
	}

	// 成长为当前的竞技场。
	v := h.curArena.base
	h.curArena.base = nBase

	// 将我们要使用的空间从保留空间转换为准备好的空间。
	sysMap(unsafe.Pointer(v), nBase-v, &memstats.heap_sys)

	// 刚刚分配的内存同时计为已释放的
	// 和空闲的，即使它尚未得到spans的支持。
	// 
	// 分配始终与堆竞技场对齐
	// 大小始终大于physPageSize，因此
	// 只需直接添加到堆中即可。
	atomic.Xadd64(&memstats.heap_released, int64(nBase-v))
	stats := memstats.heapStats.acquire()
	atomic.Xaddint64(&stats.released, int64(nBase-v))
	memstats.heapStats.release()

	// 更新页面分配器的结构，使此
	// 空间准备好分配。
	h.pages.grow(v, nBase-v)
	totalGrowth += nBase - v

	// 我们刚刚导致了堆的增长，所以清除即将使用的内容。
	// 通过清除内联，我们通过清除最不可能重复使用的内存片段来处理分配内存片段失败的问题。
	if retained := heapRetained(); retained+uint64(totalGrowth) > h.scavengeGoal {
		todo := totalGrowth
		if overage := uintptr(retained + uint64(totalGrowth) - h.scavengeGoal); todo > overage {
			todo = overage
		}
		h.pages.scavenge(todo, false)
	}
	return true
}

// 将跨度释放回堆中。
func (h *mheap) freeSpan(s *mspan) {
	systemstack(func() {
		lock(&h.lock)
		if msanenabled {
			// 告诉msan整个跨度不再使用。
			base := unsafe.Pointer(s.base())
			bytes := s.npages << _PageShift
			msanfree(base, bytes)
		}
		h.freeSpanLocked(s, spanAllocHeap)
		unlock(&h.lock)
	})
}

// freeManual释放allocManual返回的手动管理的范围。
// 类型必须与传递给
// 分配的allocManual的spanAllocType相同。
// 
// 仅当gcphase==\u GCoff时才能调用此函数。有关
// 的说明，请参见mSpanState。
// 
// 必须在系统堆栈上调用freeManual，因为它获取了
// 堆锁。有关详细信息，请参见mheap。
// 
// go:systemstack 
func (h *mheap) freeManual(s *mspan, typ spanAllocType) {
	s.needzero = 1
	lock(&h.lock)
	h.freeSpanLocked(s, typ)
	unlock(&h.lock)
}

func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) {
	assertLockHeld(&h.lock)

	switch s.state.get() {
	case mSpanManual:
		if s.allocCount != 0 {
			throw("mheap.freeSpanLocked - invalid stack free")
		}
	case mSpanInUse:
		if s.allocCount != 0 || s.sweepgen != h.sweepgen {
			print("mheap.freeSpanLocked - span ", s, " ptr ", hex(s.base()), " allocCount ", s.allocCount, " sweepgen ", s.sweepgen, "/", h.sweepgen, "\n")
			throw("mheap.freeSpanLocked - invalid free")
		}
		atomic.Xadd64(&h.pagesInUse, -int64(s.npages))

		// 清除竞技场页面位图中的正在使用位。
		arena, pageIdx, pageMask := pageIndexOf(s.base())
		atomic.And8(&arena.pageInUse[pageIdx], ^pageMask)
	default:
		throw("mheap.freeSpanLocked - invalid span state")
	}

	// 更新统计信息。
	// 
	// 在AllocPan中镜像代码。
	nbytes := s.npages * pageSize
	if typ == spanAllocHeap {
		atomic.Xadd64(&memstats.heap_inuse, -int64(nbytes))
	}
	if typ.manual() {
		// 手动管理的内存不计入heap_sys，因此请将其添加回。
		memstats.heap_sys.add(int64(nbytes))
	}
	// 更新一致的统计信息。
	stats := memstats.heapStats.acquire()
	switch typ {
	case spanAllocHeap:
		atomic.Xaddint64(&stats.inHeap, -int64(nbytes))
	case spanAllocStack:
		atomic.Xaddint64(&stats.inStacks, -int64(nbytes))
	case spanAllocPtrScalarBits:
		atomic.Xaddint64(&stats.inPtrScalarBits, -int64(nbytes))
	case spanAllocWorkBuf:
		atomic.Xaddint64(&stats.inWorkBufs, -int64(nbytes))
	}
	memstats.heapStats.release()

	// 将空间标记为空闲。
	h.pages.free(s.base(), s.npages)

	// 释放跨结构。我们不再需要它了。
	s.state.set(mSpanDead)
	h.freeMSpanLocked(s)
}

// 清除所有获取堆锁（阻止任何额外的
// 对页面分配器的操作）并迭代整个
// 堆，清除所有可用的空闲页面。
func (h *mheap) scavengeAll() {
	// 在持有堆锁时不允许malloc或panic。我们在这里使用
	// 因为这是
	// mheap API。
	gp := getg()
	gp.m.mallocing++
	lock(&h.lock)
	// 开始新一代清道夫，这样我们就有机会在整个堆上行走。
	h.pages.scavengeStartGen()
	released := h.pages.scavenge(^uintptr(0), false)
	gen := h.pages.scav.gen
	unlock(&h.lock)
	gp.m.mallocing--

	if debug.scavtrace > 0 {
		printScavTrace(gen, released, true)
	}
}

// go:linkname runtime\u debug\u freeOSMemory runtime/debug.freeOSMemory 
func runtime_debug_freeOSMemory() {
	GC()
	systemstack(func() { mheap_.scavengeAll() })
}

// 使用给定的起始和npages初始化新的跨距。
func (span *mspan) init(base uintptr, npages uintptr) {
	// 量程*未*归零。
	span.next = nil
	span.prev = nil
	span.list = nil
	span.startAddr = base
	span.npages = npages
	span.allocCount = 0
	span.spanclass = 0
	span.elemsize = 0
	span.speciallock.key = 0
	span.specials = nil
	span.needzero = 0
	span.freeindex = 0
	span.allocBits = nil
	span.gcmarkBits = nil
	span.state.set(mSpanDead)
	lockInit(&span.speciallock, lockRankMspanSpecial)
}

func (span *mspan) inList() bool {
	return span.list != nil
}

// 初始化空的双链接列表。
func (list *mSpanList) init() {
	list.first = nil
	list.last = nil
}

func (list *mSpanList) remove(span *mspan) {
	if span.list != list {
		print("runtime: failed mSpanList.remove span.npages=", span.npages,
			" span=", span, " prev=", span.prev, " span.list=", span.list, " list=", list, "\n")
		throw("mSpanList.remove")
	}
	if list.first == span {
		list.first = span.next
	} else {
		span.prev.next = span.next
	}
	if list.last == span {
		list.last = span.prev
	} else {
		span.next.prev = span.prev
	}
	span.next = nil
	span.prev = nil
	span.list = nil
}

func (list *mSpanList) isEmpty() bool {
	return list.first == nil
}

func (list *mSpanList) insert(span *mspan) {
	if span.next != nil || span.prev != nil || span.list != nil {
		println("runtime: failed mSpanList.insert", span, span.next, span.prev, span.list)
		throw("mSpanList.insert")
	}
	span.next = list.first
	if list.first != nil {
		// 列表至少包含一个span；链接它。
		// 列表中的最后一个跨距不变。
		list.first.prev = span
	} else {
		// 列表中没有跨距，因此这也是最后一个跨距。
		list.last = span
	}
	list.first = span
	span.list = list
}

func (list *mSpanList) insertBack(span *mspan) {
	if span.next != nil || span.prev != nil || span.list != nil {
		println("runtime: failed mSpanList.insertBack", span, span.next, span.prev, span.list)
		throw("mSpanList.insertBack")
	}
	span.prev = list.last
	if list.last != nil {
		// 列表至少包含一个跨度。
		list.last.next = span
	} else {
		// 列表中没有跨距，因此这也是第一个跨距。
		list.first = span
	}
	list.last = span
	span.list = list
}

// takeAll从其他文件中删除所有跨距，并将其插入列表的前面
// 。
func (list *mSpanList) takeAll(other *mSpanList) {
	if other.isEmpty() {
		return
	}

	// 将“其他”中的所有内容重新复制到列表中。
	for s := other.first; s != nil; s = s.next {
		s.list = list
	}

	// 连接列表。
	if list.isEmpty() {
		*list = *other
	} else {
		// 两个列表都不是空的。把其他的放在列表之前。
		other.last.next = list.first
		list.first.prev = other.last
		list.first = other.first
	}

	other.first, other.last = nil, nil
}

const (
	_KindSpecialFinalizer = 1
	_KindSpecialProfile   = 2
	// /\u Kind SpecialReachable是一款用于跟踪测试期间可达性的专用工具。
	_KindSpecialReachable = 3
	// 注意：终结器专用必须是第一个，因为如果我们正在释放
	// 对象，终结器专用将导致释放操作
	// 中止，如果发生这种情况，我们希望保留
	// 周围的其他特殊记录。
)

// go:notinheap 
type special struct {
	next   *special // 在span 
	offset uint16   // 对象的span偏移量
	kind   byte     // 一种特殊的
}

// span在竞技场位图中有特殊标记span。
func spanHasSpecials(s *mspan) {
	arenaPage := (s.base() / pageSize) % pagesPerArena
	ai := arenaIndex(s.base())
	ha := mheap_.arenas[ai.l1()][ai.l2()]
	atomic.Or8(&ha.pageSpecials[arenaPage/8], uint8(1)<<(arenaPage%8))
}

// spanHasNoSpecials将span标记为在竞技场中没有特殊物品。
func spanHasNoSpecials(s *mspan) {
	arenaPage := (s.base() / pageSize) % pagesPerArena
	ai := arenaIndex(s.base())
	ha := mheap_.arenas[ai.l1()][ai.l2()]
	atomic.And8(&ha.pageSpecials[arenaPage/8], ^(uint8(1) << (arenaPage % 8)))
}

// 将特殊记录s添加到
// offset&next（本例程将填写）外，s的所有字段都应填写。
// 如果特殊项添加成功，则返回true，否则返回false。
// （只有当具有相同p和s->种类的记录已存在时，添加才会失败。）
func addspecial(p unsafe.Pointer, s *special) bool {
	span := spanOfHeap(uintptr(p))
	if span == nil {
		throw("addspecial on invalid pointer")
	}

	// 确保扫描范围。
	// 扫描访问不带锁的特殊列表，因此我们有
	// 与之同步。这样更安全。
	mp := acquirem()
	span.ensureSwept()

	offset := uintptr(p) - span.base()
	kind := s.kind

	lock(&span.speciallock)

	// 找到拼接点，检查现有记录。
	t := &span.specials
	for {
		x := *t
		if x == nil {
			break
		}
		if offset == uintptr(x.offset) && kind == x.kind {
			unlock(&span.speciallock)
			releasem(mp)
			return false // 已存在
		}
		if offset < uintptr(x.offset) || (offset == uintptr(x.offset) && kind < x.kind) {
			break
		}
		t = &x.next
	}

	// 记录中的拼接，请填写偏移量。
	s.offset = uint16(offset)
	s.next = *t
	*t = s
	spanHasSpecials(span)
	unlock(&span.speciallock)
	releasem(mp)

	return true
}

// 删除对象p的给定类型的特殊记录。
// 如果记录存在，则返回该记录，否则返回零。
// 调用方必须修复释放结果。
func removespecial(p unsafe.Pointer, kind uint8) *special {
	span := spanOfHeap(uintptr(p))
	if span == nil {
		throw("removespecial on invalid pointer")
	}

	// 确保跨度被扫掠。
	// 扫描访问不带锁的特殊列表，因此我们有
	// 与之同步。这样更安全。
	mp := acquirem()
	span.ensureSwept()

	offset := uintptr(p) - span.base()

	var result *special
	lock(&span.speciallock)
	t := &span.specials
	for {
		s := *t
		if s == nil {
			break
		}
		// 此函数仅用于终结器，因此我们不检查
		// “内部”特殊值（p必须完全等于s->offset）。
		if offset == uintptr(s.offset) && kind == s.kind {
			*t = s.next
			result = s
			break
		}
		t = &s.next
	}
	if span.specials == nil {
		spanHasNoSpecials(span)
	}
	unlock(&span.speciallock)
	releasem(mp)
	return result
}

// 所描述的对象设置了终结器。
// 
// 特殊finalizer是从非GC的内存分配的，因此任何堆
// 指针都必须经过特殊处理。
// 
// go:notinheap 
type specialfinalizer struct {
	special special
	fn      *funcval // 可能是堆指针。
	nret    uintptr
	fint    *_type   // 可能是堆指针，但始终有效。
	ot      *ptrtype // 可能是堆指针，但始终有效。
}

// 向对象p添加终结器。如果成功，则返回true。
func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *_type, ot *ptrtype) bool {
	lock(&mheap_.speciallock)
	s := (*specialfinalizer)(mheap_.specialfinalizeralloc.alloc())
	unlock(&mheap_.speciallock)
	s.special.kind = _KindSpecialFinalizer
	s.fn = f
	s.nret = nret
	s.fint = fint
	s.ot = ot
	if addspecial(p, &s.special) {
		// 这负责在任何
		// GC相关不变量，其中markrootSpans可能已经运行，但标记终止尚未运行。
		// 情况下维护与markrootSpans相同的
		if gcphase != _GCoff {
			base, _, _ := findObject(uintptr(p), 0, 0)
			mp := acquirem()
			gcw := &mp.p.ptr().gcw
			// 标记从对象
			// 可以访问的所有内容，以便保留给终结器。
			scanobject(base, gcw)
			// 标记终结器本身，因为
			// special不属于GC的堆。
			scanblock(uintptr(unsafe.Pointer(&s.fn)), sys.PtrSize, &oneptrmask[0], gcw, nil)
			releasem(mp)
		}
		return true
	}

	// 有一个旧的终结器
	lock(&mheap_.speciallock)
	mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))
	unlock(&mheap_.speciallock)
	return false
}

// 从对象p中删除终结器（如果有）。
func removefinalizer(p unsafe.Pointer) {
	s := (*specialfinalizer)(unsafe.Pointer(removespecial(p, _KindSpecialFinalizer)))
	if s == nil {
		return // 没有终结器删除
	}
	lock(&mheap_.speciallock)
	mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))
	unlock(&mheap_.speciallock)
}

// 正在对所描述的对象进行堆分析。
// 
// go:notinheap 
type specialprofile struct {
	special special
	b       *bucket
}

// 将与addr关联的堆配置文件存储桶设置为b。
func setprofilebucket(p unsafe.Pointer, b *bucket) {
	lock(&mheap_.speciallock)
	s := (*specialprofile)(mheap_.specialprofilealloc.alloc())
	unlock(&mheap_.speciallock)
	s.special.kind = _KindSpecialProfile
	s.b = b
	if !addspecial(p, &s.special) {
		throw("setprofilebucket: profile already set")
	}
}

// specialReachable跟踪对象在下一个
// GC循环中是否可访问。这用于测试。
type specialReachable struct {
	special   special
	done      bool
	reachable bool
}

// specialsIter帮助迭代特价列表。
type specialsIter struct {
	pprev **special
	s     *special
}

func newSpecialsIter(span *mspan) specialsIter {
	return specialsIter{&span.specials, span.specials}
}

func (i *specialsIter) valid() bool {
	return i.s != nil
}

func (i *specialsIter) next() {
	i.pprev = &i.s.next
	i.s = *i.pprev
}

// unlinkAndNext从列表中删除当前特殊项并移动
// 下一个特殊的迭代器。它返回未链接的特殊值。
func (i *specialsIter) unlinkAndNext() *special {
	cur := i.s
	i.s = cur.next
	*i.pprev = i.s
	return cur
}

// freeSpecial对特殊s执行任何清理并解除分配。
// s必须已从特价商品列表中取消链接。
func freeSpecial(s *special, p unsafe.Pointer, size uintptr) {
	switch s.kind {
	case _KindSpecialFinalizer:
		sf := (*specialfinalizer)(unsafe.Pointer(s))
		queuefinalizer(p, sf.fn, sf.nret, sf.fint, sf.ot)
		lock(&mheap_.speciallock)
		mheap_.specialfinalizeralloc.free(unsafe.Pointer(sf))
		unlock(&mheap_.speciallock)
	case _KindSpecialProfile:
		sp := (*specialprofile)(unsafe.Pointer(s))
		mProf_Free(sp.b, size)
		lock(&mheap_.speciallock)
		mheap_.specialprofilealloc.free(unsafe.Pointer(sp))
		unlock(&mheap_.speciallock)
	case _KindSpecialReachable:
		sp := (*specialReachable)(unsafe.Pointer(s))
		sp.done = true
		// 造物主释放了这些。
	default:
		throw("bad special kind")
		panic("not reached")
	}
}

// gcBits是一种alloc/mark位图。这始终用作*gcbit。
// 
// go:notinheap 
type gcBits uint8

// bytep返回指向b的第n个字节的指针。
func (b *gcBits) bytep(n uintptr) *uint8 {
	return addb((*uint8)(b), n)
}

// bitp返回一个指向字节的指针，该字节包含位n和
// 的掩码，用于从*bytep中选择该位。
func (b *gcBits) bitp(n uintptr) (bytep *uint8, mask uint8) {
	return b.bytep(n / 8), 1 << (n % 8)
}

const gcBitsChunkBytes = uintptr(64 << 10)
const gcBitsHeaderBytes = unsafe.Sizeof(gcBitsHeader{})

type gcBitsHeader struct {
	free uintptr // free是下一个空闲字节的位索引。
	next uintptr // /*gcBits触发递归类型错误。（第14620期）
}

// go:notinheap 
type gcBitsArena struct {
	// gcBitsHeader 
	free uintptr // free是下一个空闲字节的位索引；原子读/写
	next *gcBitsArena
	bits [gcBitsChunkBytes - gcBitsHeaderBytes]gcBits
}

var gcBitsArenas struct {
	lock     mutex
	free     *gcBitsArena
	next     *gcBitsArena // 原子读。在锁定状态下以原子方式写入。
	current  *gcBitsArena
	previous *gcBitsArena
}

// tryAlloc从b分配，如果b没有足够的空间，则返回nil。
// 这可以安全地同时调用。
func (b *gcBitsArena) tryAlloc(bytes uintptr) *gcBits {
	if b == nil || atomic.Loaduintptr(&b.free)+bytes > uintptr(len(b.bits)) {
		return nil
	}
	// 尝试从此块分配。有足够的空间。
	end := atomic.Xadduintptr(&b.free, bytes)
	if end > uintptr(len(b.bits)) {
		return nil
	}
	start := end - bytes
	return &b.bits[start]
}

// newMarkBits返回指向8字节对齐字节的指针
// 用于span的标记位。
func newMarkBits(nelems uintptr) *gcBits {
	blocksNeeded := uintptr((nelems + 63) / 64)
	bytesNeeded := blocksNeeded * 8

	// 尝试直接从当前head arena分配。
	head := (*gcBitsArena)(atomic.Loadp(unsafe.Pointer(&gcBitsArenas.next)))
	if p := head.tryAlloc(bytesNeeded); p != nil {
		return p
	}

	// 头部竞技场没有足够的空间。我们可能需要分配一个新的竞技场。
	lock(&gcBitsArenas.lock)
	// 再次尝试头部竞技场，因为它可能已经改变。现在
	// 我们持有锁，列表头不能更改，但其
	// 自由位置仍然可以更改。
	if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
		unlock(&gcBitsArenas.lock)
		return p
	}

	// 分配一个新竞技场。这可能会暂时关闭锁。
	fresh := newArenaMayUnlock()
	// 如果newArenaMayUnlock放弃了锁，另一个线程可能
	// 将一个新竞技场放在了“下一个”列表中。再次尝试从下一步分配
	// 。
	if p := gcBitsArenas.next.tryAlloc(bytesNeeded); p != nil {
		// 将fresh重新列入免费名单。
		// TODO:将其标记为“已归零”
		fresh.next = gcBitsArenas.free
		gcBitsArenas.free = fresh
		unlock(&gcBitsArenas.lock)
		return p
	}

	// 从新竞技场分配。我们还没有把它联系起来，所以
	// 这是不可能的比赛，而且肯定会成功。
	p := fresh.tryAlloc(bytesNeeded)
	if p == nil {
		throw("markBits overflow")
	}

	// 将新竞技场添加到“下一个”列表中。
	fresh.next = gcBitsArenas.next
	atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), unsafe.Pointer(fresh))

	unlock(&gcBitsArenas.lock)
	return p
}

// newAllocBits返回指向8字节对齐字节的指针
// 用于此范围的alloc位。
// newAllocBits用于提供新初始化的跨距
// 分配位。对于未初始化的跨距，当扫描跨距时，标记位重新用作分配位。
func newAllocBits(nelems uintptr) *gcBits {
	return newMarkBits(nelems)
}

// nextMarkBitArenaEpoch为竞技场建立了一个新纪元
// 持有标志位。竞技场的命名与
// /当前GC循环相关，当前GC循环通过调用finishweep_m来划分。
// 
// 所有当前跨距均已扫掠。
// 在该扫描过程中，每个跨度为其在
// gcBitsArenas.next块中的gcmarkBits分配了空间。gcBitsArenas.next变为gcBitsArenas.current 
// GC将在其中标记对象，在每个跨度扫描后，这些位
// 将用于分配对象。
// gcBitsArenas.current变为gcBitsArenas.previous，其中跨距的
// gcAllocBits处于活动状态，直到在此GC循环中扫描完所有跨距。
// 通过将gcallocbit指向gcBitsArenas.current，span的扫描将取消对gcBitsArenas.previous 
// 的所有引用。
// gcBitsArenas.previous被发布到gcBitsArenas.free列表中。
func nextMarkBitArenaEpoch() {
	lock(&gcBitsArenas.lock)
	if gcBitsArenas.previous != nil {
		if gcBitsArenas.free == nil {
			gcBitsArenas.free = gcBitsArenas.previous
		} else {
			// 查找以前竞技场的结尾。
			last := gcBitsArenas.previous
			for last = gcBitsArenas.previous; last.next != nil; last = last.next {
			}
			last.next = gcBitsArenas.free
			gcBitsArenas.free = gcBitsArenas.previous
		}
	}
	gcBitsArenas.previous = gcBitsArenas.current
	gcBitsArenas.current = gcBitsArenas.next
	atomic.StorepNoWB(unsafe.Pointer(&gcBitsArenas.next), nil) // newMarkBits在需要时调用newArena 
	unlock(&gcBitsArenas.lock)
}

// newArenaMayUnlock分配gcBits竞技场并将其归零。
// 调用方必须持有gcBitsArena.lock。这可能会暂时释放它。
func newArenaMayUnlock() *gcBitsArena {
	var result *gcBitsArena
	if gcBitsArenas.free == nil {
		unlock(&gcBitsArenas.lock)
		result = (*gcBitsArena)(sysAlloc(gcBitsChunkBytes, &memstats.gcMiscSys))
		if result == nil {
			throw("runtime: cannot allocate memory")
		}
		lock(&gcBitsArenas.lock)
	} else {
		result = gcBitsArenas.free
		gcBitsArenas.free = gcBitsArenas.free.next
		memclrNoHeapPointers(unsafe.Pointer(result), gcBitsChunkBytes)
	}
	result.next = nil
	// 如果result.bits未对齐8字节，请调整索引，使&result.bits[result.free]对齐8字节。
	if uintptr(unsafe.Offsetof(gcBitsArena{}.bits))&7 == 0 {
		result.free = 0
	} else {
		result.free = 8 - (uintptr(unsafe.Pointer(&result.bits[0])) & 7)
	}
	return result
}
